//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Globalization;
using System.Collections.Generic;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Resources;


namespace EffiProz.Core.DbInfos
{
    /**
     * Provides extended information about HSQLDB tables and their
     * columns/indices. <p>
     *
     * @author boucherb@users
     * @version 1.8.0
     * @since 1.7.2
     */
    public sealed class DITableInfo
    {

        // related to DatabaseMetaData
        public int bestRowTemporary = 0;
        public int bestRowTransaction = 1;
        public int bestRowSession = 2;
        public int bestRowUnknown = 0;
        public int bestRowNotPseudo = 1;
        public const short tableIndexOther = 3;

        /** Used _in buffer size and character octet length determinations. */
        private const int HALF_MAX_INT = (int)((uint)int.MaxValue) >> 1;

        /** BundleHandler id for column remarks resource bundle. */
        private int hnd_column_remarks = -1;

        /** BundleHandler id for table remarks resource bundle. */
        private int hnd_table_remarks = -1;

        /** The Table object upon which this object is reporting. */
        private Table table;

        /** Provides intrinsic type infformation support. */
        private static DITypeInfo ti = new DITypeInfo();

        /**
         * Creates a new DITableInfo object with the default Locale and reporting
         * on no table.  It is absolutely essential the a valid Table object is
         * assigned to this object, using the setTable method, before any Table,
         * Column or Index oriented value retrieval methods are called; this class
         * contains no assertions or exception handling related to a null or
         * invalid table member attribute.
         */
        public DITableInfo()
        {

            /** @todo fredt - remove from here: should be set _in a database-wide context */
            setLocale(CultureInfo.CurrentCulture);
        }

        /**
         * Sets the Locale for table and column remarks. <p>
         *
         * @param l The Locale object
         */
        public void setLocale(CultureInfo l)
        {

            CultureInfo oldLocale;

            lock (typeof(BundleHandler))
            {
                oldLocale = BundleHandler.getLocale();

                BundleHandler.setLocale(l);

                hnd_column_remarks =
                    BundleHandler.getBundleHandle("column-remarks", null);
                hnd_table_remarks = BundleHandler.getBundleHandle("table-remarks",
                        null);

                BundleHandler.setLocale(oldLocale);
            }
        }

        /**
         * Retrieves whether the best row identifier column is
         * a pseudo column, like an Oracle ROWID. <p>
         *
         * Currently, this always returns an int whose value is
         * DatabaseMetaData.bestRowNotPseudo, as HSQLDB does not support
         * pseudo columns such as ROWID. <p>
         *
         * @return whether the best row identifier column is
         * a pseudo column
         */
        public int getBRIPseudo()
        {
            return (bestRowNotPseudo);
        }

        /**
         * Retrieves the scope of the best row identifier. <p>
         *
         * This : the rules described in
         * DatabaseInformationMain.SYSTEM_BESTROWIDENTIFIER. <p>
         *
         * @return the scope of the best row identifier
         */
        public int getBRIScope()
        {
            return (table.isWritable()) ? (bestRowTemporary)
                                        : (bestRowSession);
        }

        /**
           * Retrieves, if definitely known, the transfer size for values of the
           * specified column, in bytes. <p>
           *
           * @param i zero-based column index
           * @return the transfer size for values of the
           * specified column, in bytes
           */
        public int? getColBufLen(int i)
        {

            int size;
            int type;
            ColumnSchema column;

            column = table.getColumn(i);
            type = column.getDataType().getAdoTypeCode();

            switch (type)
            {

                case Types.SQL_CHAR:
                case Types.SQL_CLOB:
                case Types.VARCHAR_IGNORECASE:
                case Types.SQL_VARCHAR:
                    {
                        size = column.getDataType().precision > int.MaxValue
                               ? int.MaxValue
                               : (int)column.getDataType().precision;

                        if (size == 0) { }
                        else if (size > HALF_MAX_INT)
                        {
                            size = 0;
                        }
                        else
                        {
                            size = 2 * size;
                        }

                        break;
                    }
                case Types.SQL_BINARY:
                case Types.SQL_BLOB:
                case Types.SQL_VARBINARY:
                    {
                        size = column.getDataType().precision > int.MaxValue
                               ? int.MaxValue
                               : (int)column.getDataType().precision;

                        break;
                    }
                case Types.SQL_BIGINT:
                case Types.SQL_DOUBLE:
                case Types.SQL_FLOAT:
                case Types.SQL_DATE:
                case Types.SQL_REAL:
                case Types.SQL_TIME_WITH_TIME_ZONE:
                case Types.SQL_TIME:
                    {
                        size = 8;

                        break;
                    }
                case Types.SQL_TIMESTAMP_WITH_TIME_ZONE:
                case Types.SQL_TIMESTAMP:
                    {
                        size = 12;

                        break;
                    }
                case Types.SQL_INTEGER:
                case Types.SQL_SMALLINT:
                case Types.TINYINT:
                    {
                        size = 4;

                        break;
                    }
                case Types.SQL_BOOLEAN:
                    {
                        size = 1;

                        break;
                    }
                default:
                    {
                        size = 0;

                        break;
                    }
            }

            return (size > 0) ? (int?)(size)
                              : null;
        }

        /**
         * Retrieves the declared size, in bytes, for character-valued
         * columns. <p>
         *
         * If the size cannot be represented in the range [0,int.MaxValue],
         * this returns null. <p>
         *
         * @param i zero-based column index
         * @return the size, in bytes, for character-valued columns
         */
        public int? getColCharOctLen(int i)
        {

            int size;
            int type;
            ColumnSchema column;

            column = table.getColumn(i);
            type = column.getDataType().getAdoTypeCode();

            switch (type)
            {

                case Types.SQL_CHAR:
                case Types.SQL_CLOB:
                case Types.VARCHAR_IGNORECASE:
                case Types.SQL_VARCHAR:
                    {
                        size = column.getDataType().precision > int.MaxValue
                               ? int.MaxValue
                               : (int)column.getDataType().precision;

                        if (size == 0) { }
                        else if (size > HALF_MAX_INT)
                        {
                            size = 0;
                        }
                        else
                        {
                            size = 2 * size;
                        }

                        break;
                    }
                default:
                    {
                        size = 0;

                        break;
                    }
            }

            return (size == 0) ? (int?)null
                               : (size);
        }

        /**
         * Retrieves the SQL data type code for the specified column. <p>
         *
         * @param i zero-based column index
         * @return the SQL data type code for the specified column
         */
        public int getColAdoDataType(int i)
        {
            return (
                table.getColumn(i).getDataType().getAdoTypeCode());
        }

        /**
         * Retrieves the SQL data type name for the specified column. <p>
         *
         * @param i zero-based column index
         * @return the SQL data type name for the specified column
         */
        public String getColDataTypeName(int i)
        {
            return table.getColumn(i).getDataType().getNameString();
        }

        /**
         * Retrieves the HSQLDB data subtype code for the specified column. <p>
         *
         * @param i zero-based column index
         * @return the HSQLDB data subtype code for the specified column
         */
        public int getColDataTypeSub(int i)
        {

            int type = table.getColumn(i).getDataType().getAdoTypeCode();
            int sub = type == Types.VARCHAR_IGNORECASE ? Types.TYPE_SUB_IGNORECASE
                                                       : Types.TYPE_SUB_DEFAULT;

            return (sub);
        }

        /**
         * Retrieves the declared default value expression for the column. <p>
         *
         * @param i zero-based column index
         * @return the declared default value expression for the column
         */
        public String getColDefault(int i)
        {
            return table.getColumn(i).getDefaultSQL();
        }

        /**
         * Retrieves whether the specified column is the identity column for
         * the table. <p>
         *
         * @param i zero-based column index
         * @return whether the specified column is the identity column for
         * the table.
         */
        public Boolean getColIsIdentity(int i)
        {
            return table.getColumn(i).isIdentity() ? true
                                                   : false;
        }

        /**
         * Retrieves whether the specified column is nullable. <p>
         *
         * If the column is nullable, "YES" is retrieved, else "NO". <p>
         *
         * @param i zero-based column index
         * @return the nullability of the specified column
         */
        public String getColIsNullable(int i)
        {

            ColumnSchema column = table.getColumn(i);

            return (column.isNullable() && !column.isPrimaryKey()) ? "YES"
                                                                   : "NO";
        }

        /**
         * Retrieves the simple name of the specified column. <p>
         *
         * @param i zero-based column index
         * @return the simple name of the specified column.
         */
        String getColName(int i)
        {
            return table.getColumn(i).getName().name;
        }

        /**
         * Retrieves the specified column's nullablility. <p>
         *
         * @param i zero-based column index
         * @return the specified column's nullablilit
         */
        public int getColNullability(int i)
        {

            ColumnSchema column = table.getColumn(i);

            return (column.isNullable() && !column.isPrimaryKey())
                   ? (DITypeInfo.columnNullable)
                   : (DITypeInfo.columnNoNulls);
        }

        /**
         * Retrieves the number base that should be used to interpret the
         * specified column's numeric precision, as reported by getColSize(int).
         *
         * @param i zero-based column index
         * @return the number base that should be used to
         *    interpret the column's numeric precision,
         *    as reported by getColSize(int).
         */
        public int? getColPrecRadix(int i)
        {

            ti.setTypeCode(table.getColumn(i).getDataType().getAdoTypeCode());

            return ti.getNumPrecRadix();
        }

        /**
         * Retrieves the remarks, if any, recorded against the specified
         * column. <p>
         *
         * @param i zero-based column index
         * @return the remarks recorded against the specified column.
         */
        public String getColRemarks(int i)
        {

            String key;

            if (table.getTableType() != TableBase.SYSTEM_TABLE)
            {
                return null;
            }

            key = getName() + "_" + getColName(i);

            return BundleHandler.getString(hnd_column_remarks, key);
        }

        /**
         * Retrieves the declared (but not currently enforced) or implicit fixed
         * number of digits to the right of the decimal point for exact numeric
         * types.
         *
         * If the column's type precludes scale declaration, null is returned.
         *
         * @param i zero-based column index
         * @return the fixed number of digits to the right of the decimal point
         * for exact numeric types.
         */
        public int? getColScaleOrNull(int i)
        {

            ColumnSchema column;
            int type;

            column = table.getColumn(i);
            type = column.getDataType().getAdoTypeCode();

            return Types.acceptsScaleCreateParam(type)
                   ? (int?)(column.getDataType().scale)
                   : null;
        }

        /**
         * Retrieves null (not implemented). <p>
         *
         * @param i zero-based column index
         * @return null (not implemented)
         */
        public String getColScopeCat(int i)
        {
            return null;
        }

        /**
         * Retrieves null (not implemented). <p>
         *
         * @param i zero-based column index
         * @return null (not implemented)
         */
        public String getColScopeSchem(int i)
        {
            return null;
        }

        /**
         * Retrieves null (not implemented). <p>
         *
         * @param i zero-based column index
         * @return null (not implemented)
         */
        public String getColScopeTable(int i)
        {
            return null;
        }

        /**
         * Retrieves either the declared or maximum length/precision for
         * the specified column, if its type allows a precision/length
         * declaration, else null. <p>
         *
         * @param i zero-based column index
         * @return the declared or maximum length/precision for
         *    the specified column
         */
        public int? getColSize(int i)
        {

            ColumnSchema column;
            int type;
            int size;

            column = table.getColumn(i);
            type = column.getDataType().getAdoTypeCode();

            if (!Types.acceptsPrecision(type))
            {
                return null;
            }

            size = column.getDataType().precision > int.MaxValue
                   ? int.MaxValue
                   : (int)column.getDataType().precision;

            if (size > 0)
            {
                return (size);
            }
            else
            {
                ti.setTypeCode(type);

                return ti.getPrecision();
            }
        }

        /**
         * Retrieves the SQL CLI data type code for the specified column. <p>
         *
         * @param i zero-based column index
         * @return the SQL CLI data type code for the specified column
         */
        public int? getColSqlDataType(int i)
        {

            ti.setTypeCode(table.getColumn(i).getDataType().getAdoTypeCode());

            return ti.getSqlDataType();
        }

        /**
         * Retrieves the SQL CLI datetime subtype for the specified column. <p>
         *
         * @param i zero-based column index
         * @return the SQL CLI datetime subtype for the specified column
         */
        public int? getColSqlDateTimeSub(int i)
        {

            ti.setTypeCode(table.getColumn(i).getDataType().typeCode);

            return ti.getSqlDateTimeSub();
        }

        /**
         * Retrieves the full data source descriptor for [TEMP] TEXT tables. <p>
         *
         * @return the full data source descriptor
         */
        String getDataSource()
        {
            return table.isText() ? ((TextTable)table).getDataSource()
                                  : null;
        }

        /**
         * Retrieves the HSQLDB-specific type of the table. <p>
         *
         * @return the HSQLDB-specific type of the table
         */
        public String getHsqlType()
        {

            switch (table.getTableType())
            {

                case TableBase.MEMORY_TABLE:
                case TableBase.TEMP_TABLE:
                case TableBase.SYSTEM_TABLE:
                    return "MEMORY";

                case TableBase.CACHED_TABLE:
                    return "CACHED";

                case TableBase.TEMP_TEXT_TABLE:
                case TableBase.TEXT_TABLE:
                    return "TEXT";

                case TableBase.VIEW_TABLE:
                default:
                    return null;
            }
        }

        /**
         * Retrieves null (not implemented). <p>
         *
         * @param i zero-based index specifier
         * @return null (not implemented)
         */
        public int? getIndexCardinality(int i)
        {
            return null;
        }

        /**
         * Retrieves the sort-direction for the specified column in the
         * specified index. <p>
         *
         * @param i zero-based index specifier
         * @param columnPosition zero-based ordinal position of column in index
         * @return the sort-direction for the specified column in the
         * specified index
         */
        public String getIndexColDirection(int i, int columnPosition)
        {

            // so far, hsqldb only supports completely ascending indexes
            return "A";
        }

        /**
         * Retrieves an array map from the zero-based ordinal positions of the
         * columns in the specfied Index to the zero-based ordinal positions of
         * the same columns in the table. <p>
         *
         * @param i zero-based index specifier
         * @return an array map from the zero-based ordinal positions of
         *    the columns in the specfied Index to the zero-based
         *    ordinal positions of the same columns in the table
         */
        public int[] getIndexColumns(int i)
        {
            return table.getIndex(i).getColumns();
        }

        /**
         * Retrieves the simple name of the specified Index. <p>
         *
         * @param i zero-based index specifier
         * @return the simple name of the specified Index
         */
        public String getIndexName(int i)
        {
            return table.getIndex(i).getName().name;
        }

        /**
         * Retrieves null (not implemented). <p>
         *
         * @param i zero-based index specifier
         * @return null (not implemented)
         */
        public int? getIndexRowCardinality(int i)
        {
            return null;
        }

        /**
         * Retrieves the DatabaseMetaData type code of the specified Index. <p>
         *
         * @param i zero-based index specifier
         * @return the DatabaseMetaData type code of the specified Index
         */
        public int? getIndexType(int i)
        {
            return (tableIndexOther);
        }

        /**
         * Retrieves the number of visible columns in the specified Index.  That
         * is, this retrieves one less than the physical number of columns if the
         * table maintains an public primary index on an public identity
         * column, as is the case when the table has no declared primary key or
         * identity column. <p>
         *
         * @param i zero-based index specifier
         * @return the number of visible columns in the specified Index
         */
        public int getIndexVisibleColumns(int i)
        {
            return table.getIndex(i).getColumnCount();
        }

        /**
         * Retrieves the simple name of the table. <p>
         *
         * @return the simple name of the table
         */
        public String getName()
        {
            return table.getName().name;
        }

        /**
         * Retrieves the value of the next automatically assigned identity. <p>
         *
         * Be aware that this is not necessarily the value that will be assigned
         * to the identity column during the next insert or update.  This value is
         * used if NULL is either implicitly or explicity assigned. <p>
         *
         * @return the value of the next automatically assigned identity
         */
        public long? getNextIdentity()
        {

            if (table.hasIdentityColumn())
            {
                return table.getNextIdentity();
            }
            else
            {
                return null;
            }
        }

        /**
         * Retrieves the remarks (if any) recorded against the Table. <p>
         *
         * @return the remarks recorded against the Table
         */
        public String getRemark()
        {

            return (table.getTableType() == TableBase.SYSTEM_TABLE)
                   ? BundleHandler.getString(hnd_table_remarks, getName())
                   : null;
        }

        /**
         * Retrieves the standard JDBC type of the table. <p>
         *
         * "TABLE" for user-defined tables, "VIEW" for user-defined views,
         * and so on.
         *
         * @return the standard JDBC type of the table
         */
        public String getJDBCStandardType()
        {

            switch (table.getTableType())
            {

                case TableBase.VIEW_TABLE:
                    return "VIEW";

                case TableBase.TEMP_TABLE:
                case TableBase.TEMP_TEXT_TABLE:
                    return "GLOBAL TEMPORARY";

                case TableBase.SYSTEM_TABLE:
                    return "SYSTEM TABLE";

                default:
                    if (table.getOwner().isSystem())
                    {
                        return "SYSTEM TABLE";
                    }
                    return "TABLE";
            }
        }

        /**
         * Retrieves the Table object on which this object is currently
         * reporting. <p>
         *
         * @return the Table object on which this object
         *    is currently reporting
         */
        public Table getTable()
        {
            return this.table;
        }

        /**
         * Retrieves, for [TEMP] TEXT tables, whether the table's data source
         * descriptor requests descending read semantics.  That is, when this
         * value is true, it indicate that the text file is to be read from
         * the bottom up. <p>
         *
         * @return whether the table's data source
         *    descriptor requests descending
         *    read semantics
         */
        public Boolean isDataSourceDescending()
        {

            if (table.isText())
            {
                return ((TextTable)table).isDescDataSource() ? true
                                                              : false;
            }

            return false;
        }

        /**
         * Retrieves whether the specified Index is non-unique. <p>
         *
         * @param i zero-based index specifier
         * @return whether the specified Index is non-unique
         */
        public Boolean isIndexNonUnique(int i)
        {
            return !table.getIndex(i).isUnique();
        }

        /**
         * Retrieves whether the table is in data read-only mode.  This value does
         * not reflect the various read-only modes of the database or the
         * read-only mode of the connection. <p>
         *
         * @return whether the table is in data read-only mode
         */
        public Boolean isReadOnly()
        {
            return table.isDataReadOnly();
        }

        /**
         * Assigns the Table object on which this object is to report. <p>
         *
         * @param table the Table object on which this object is to report
         */
        public void setTable(Table table)
        {
            this.table = table;
        }
    }
}

